home *** CD-ROM | disk | FTP | other *** search
/ PC World Komputer 2010 April / PCWorld0410.iso / hity wydania / Ubuntu 9.10 PL / karmelkowy-koliberek-desktop-9.10-i386-PL.iso / casper / filesystem.squashfs / usr / bin / unattended-upgrade < prev    next >
Text File  |  2009-07-20  |  17KB  |  449 lines

  1. #!/usr/bin/python
  2. # Copyright (c) 2005-2009 Canonical Ltd
  3. #
  4. # AUTHOR:
  5. # Michael Vogt <mvo@ubuntu.com>
  6. #
  7. # This file is part of unattended-upgrades
  8. #
  9. # unattended-upgrades is free software; you can redistribute it and/or
  10. # modify it under the terms of the GNU General Public License as published
  11. # by the Free Software Foundation; either version 2 of the License, or (at
  12. # your option) any later version.
  13. #
  14. # unattended-upgrades is distributed in the hope that it will be useful,
  15. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  16. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  17. # General Public License for more details.
  18. #
  19. # You should have received a copy of the GNU General Public License
  20. # along with unattended-upgrades; if not, write to the Free Software
  21. # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
  22. #
  23.  
  24. import apt_inst
  25. import apt_pkg
  26.  
  27. import sys
  28. import os
  29. import string
  30. import datetime
  31. import ConfigParser
  32.  
  33. from StringIO import StringIO
  34. from optparse import OptionParser
  35. from subprocess import Popen, PIPE
  36.  
  37. import warnings
  38. warnings.filterwarnings("ignore", "apt API not stable yet", FutureWarning)
  39. import apt
  40. import logging
  41. import subprocess
  42.  
  43. import gettext
  44. from gettext import gettext as _
  45.  
  46. class MyCache(apt.Cache):
  47.     def __init__(self):
  48.         apt.Cache.__init__(self)
  49.     def clear(self):
  50.         self._depcache.Init()
  51.         assert (self._depcache.InstCount == 0 and 
  52.                 self._depcache.BrokenCount == 0 and
  53.         self._depcache.DelCount == 0)
  54.         
  55.  
  56. def is_allowed_origin(pkg, allowed_origins):
  57.     if not pkg.candidate:
  58.         return False
  59.     for origin in pkg.candidate.origins:
  60.         for allowed in allowed_origins:
  61.             if origin.origin == allowed[0] and origin.archive == allowed[1]:
  62.                 return True
  63.     return False
  64.  
  65. def check_changes_for_sanity(cache, allowed_origins, blacklist):
  66.     if cache._depcache.BrokenCount != 0:
  67.         return False
  68.     for pkg in cache:
  69.         if pkg.markedDelete:
  70.             logging.debug("pkg '%s' now marked delete" % pkg.name)
  71.             return False
  72.         if pkg.markedInstall or pkg.markedUpgrade:
  73.             if not is_allowed_origin(pkg, allowed_origins):
  74.                 logging.debug("pkg '%s' not in allowed origin" % pkg.name)
  75.                 return False
  76.             if pkg.name in blacklist:
  77.                 logging.debug("pkg '%s' blacklisted" % pkg.name)
  78.                 return False
  79.             if pkg._pkg.SelectedState == apt_pkg.SelStateHold:
  80.                 logging.debug("pkg '%s' is on hold" % pkg.name)
  81.                 return False
  82.     return True
  83.  
  84. def pkgname_from_deb(debfile):
  85.     # FIXME: add error checking here
  86.     try:
  87.         control = apt_inst.debExtractControl(open(debfile))
  88.         sections = apt_pkg.ParseSection(control)
  89.         return sections["Package"]
  90.     except (IOError, SystemError), e:
  91.         logging.error("failed to read deb file '%s' (%s)" % (debfile, e))
  92.         # dumb fallback
  93.         return debfile.split("_")[0]
  94.  
  95. def conffile_prompt(destFile):
  96.     logging.debug("check_conffile_prompt('%s')" % destFile)
  97.     pkgname = pkgname_from_deb(destFile)
  98.     status_file = apt_pkg.Config.Find("Dir::State::status")
  99.     parse = apt_pkg.ParseTagFile(open(status_file,"r"))
  100.     while parse.Step() == 1:
  101.         if parse.Section.get("Package") == pkgname:
  102.             logging.debug("found pkg: %s" % pkgname)
  103.             if parse.Section.has_key("Conffiles"):
  104.                 conffiles = parse.Section.get("Conffiles")
  105.                 # Conffiles:
  106.                 # /etc/bash_completion.d/m-a c7780fab6b14d75ca54e11e992a6c11c
  107.                 for line in string.split(conffiles,"\n"):
  108.                     logging.debug("conffile line: %s", line)
  109.                     l = string.split(string.strip(line))
  110.                     file = l[0]
  111.                     md5 = l[1]
  112.                     if len(l) > 2:
  113.                         obs = l[2]
  114.                     else:
  115.                         obs = None
  116.                     if os.path.exists(file) and obs != "obsolete":
  117.                         current_md5 = apt_pkg.md5sum(open(file).read())
  118.                         if current_md5 != md5:
  119.                             return True
  120.     return False
  121.  
  122.  
  123. def dpkg_conffile_prompt():
  124.     if not apt_pkg.Config.has_key("DPkg::Options"):
  125.         return True
  126.     options = apt_pkg.Config.ValueList("DPkg::Options")
  127.     for option in map(string.strip, options):
  128.         if (option == "--force-confold" or
  129.         option == "--force-confnew"):
  130.             return False
  131.     return True
  132.  
  133. def rewind_cache(cache, pkgs_to_upgrade):
  134.     " set the cache back to the state with packages_to_upgrade "
  135.     cache.clear()
  136.     for pkg2 in pkgs_to_upgrade:
  137.         pkg2.markUpgrade()
  138.  
  139. def host():
  140.     return os.uname()[1]
  141.  
  142. # *sigh* textwrap is nice, but it breaks "linux-image" into two
  143. # seperate lines
  144. def wrap(t, width=70, subsequent_indent=""):
  145.     out = ""
  146.     for s in t.split():
  147.         if (len(out)-out.rfind("\n")) + len(s) > width:
  148.             out += "\n" + subsequent_indent
  149.         out += s + " "
  150.     return out
  151.  
  152. def setup_apt_listchanges():
  153.     " deal with apt-listchanges "
  154.     conf = "/etc/apt/listchanges.conf"
  155.     if os.path.exists(conf):
  156.         # check if mail is used by apt-listchanges
  157.         cf = ConfigParser.ConfigParser()
  158.         cf.read(conf)
  159.         if cf.has_section("apt") and cf.has_option("apt","frontend"):
  160.             frontend = cf.get("apt","frontend")
  161.             if frontend == "mail" and os.path.exists("/usr/sbin/sendmail"):
  162.                 # mail frontend and sendmail, we are fine
  163.                 logging.debug("apt-listchanges is set to mail frontend, ignoring")
  164.                 return
  165.     # setup env (to play it safe) and return
  166.     os.putenv("APT_LISTCHANGES_FRONTEND","none");
  167.  
  168. def send_summary_mail(pkgs, res, pkgs_kept_back, mem_log, logfile_dpkg):
  169.     " send mail (if configured in Unattended-Upgrades::Mail) "
  170.     email = apt_pkg.Config.Find("Unattended-Upgrade::Mail", "")
  171.     if not email:
  172.         return
  173.     if not os.path.exists("/usr/bin/mail"):
  174.         logging.error(_("No '/usr/bin/mail', can not send mail. "
  175.                         "You probably want to install the 'mailx' package."))
  176.         return
  177.     logging.debug("Sending mail with '%s' to '%s'" % (logfile_dpkg, email))
  178.     mail = subprocess.Popen(["/usr/bin/mail",
  179.                              "-s", _("unattended-upgrades result "
  180.                                      "for '%s'") % host(), 
  181.                              email],
  182.                             stdin=subprocess.PIPE)
  183.     s = _("Unattended upgrade returned: %s\n\n") % res
  184.     s += _("Packages that are upgraded:\n")
  185.     s += " " + wrap(pkgs, 70, " ")
  186.     s += "\n"
  187.     if pkgs_kept_back:
  188.         s += _("Packages with upgradable origin but kept back:\n")
  189.         s += " " + wrap(" ".join(pkgs_kept_back), 70, " ")
  190.         s += "\n"
  191.     s += "\n"
  192.     s += _("Package installation log:")+"\n"
  193.     s += open(logfile_dpkg).read()
  194.     s += "\n\n"
  195.     s += _("Unattended-upgrades log:\n")
  196.     s += mem_log.getvalue()
  197.     mail.stdin.write(s)
  198.     mail.stdin.close()
  199.     ret = mail.wait()
  200.     logging.debug("mail returned: %s" % ret)
  201.     
  202.  
  203. def main():
  204.     # init the options
  205.     parser = OptionParser()
  206.     parser.add_option("-d", "--debug",
  207.                       action="store_true", dest="debug", default=False,
  208.                       help=_("print debug messages"))
  209.     parser.add_option("", "--dry-run",
  210.                       action="store_true", default=False,
  211.                       help=_("Simulation, download but do not install"))
  212.     (options, args) = parser.parse_args()
  213.  
  214.     # setup logging
  215.     logger = logging.getLogger()
  216.     mem_log = StringIO()
  217.     if options.debug:
  218.         logger.setLevel(logging.DEBUG)
  219.         stderr_handler = logging.StreamHandler()
  220.         logger.addHandler(stderr_handler)
  221.     if apt_pkg.Config.Find("Unattended-Upgrade::Mail", ""):
  222.         mem_log_handler = logging.StreamHandler(mem_log)
  223.         logger.addHandler(mem_log_handler)
  224.  
  225.     # format (origin, archive), e.g. ("Ubuntu","dapper-security")
  226.     allowed_origins = map(string.split, apt_pkg.Config.ValueList("Unattended-Upgrade::Allowed-Origins"))
  227.  
  228.     # pkgs that are (for some reason) not save to install
  229.     blacklisted_pkgs = apt_pkg.Config.ValueList("Unattended-Upgrade::Package-Blacklist")
  230.     logging.info(_("Initial blacklisted packages: %s"), " ".join(blacklisted_pkgs))
  231.     logging.info(_("Starting unattended upgrades script"))
  232.  
  233.     # display available origin
  234.     logging.info(_("Allowed origins are: %s") % map(str,allowed_origins))
  235.     
  236.     # check and get lock
  237.     try:
  238.         apt_pkg.PkgSystemLock()
  239.     except SystemError, e:
  240.         logging.error(_("Lock could not be acquired (another package "
  241.                         "manager running?)"))
  242.         print _("Cache lock can not be acquired, exiting")
  243.         sys.exit(1)
  244.  
  245.     # get a cache
  246.     cache = MyCache()
  247.     if cache._depcache.BrokenCount > 0:
  248.         print _("Cache has broken packages, exiting")
  249.         logging.error(_("Cache has broken packages, exiting"))
  250.         sys.exit(1)
  251.     # speed things up with latest apt
  252.     actiongroup = apt_pkg.GetPkgActionGroup(cache._depcache)
  253.  
  254.     # find out about the packages that are upgradable (in a allowed_origin)
  255.     pkgs_to_upgrade = []
  256.     pkgs_kept_back = []
  257.     for pkg in cache:
  258.         if options.debug and pkg.isUpgradable:
  259.             logging.debug("Checking: %s (%s)" % (pkg.name, map(str, pkg.candidate.origins)))
  260.         if (pkg.isUpgradable and 
  261.         is_allowed_origin(pkg,allowed_origins)):
  262.             try:
  263.                 pkg.markUpgrade()
  264.                 if check_changes_for_sanity(cache, allowed_origins,
  265.                                             blacklisted_pkgs):
  266.                     pkgs_to_upgrade.append(pkg)
  267.                 else:
  268.                     logging.debug("sanity check failed")
  269.                     rewind_cache(cache, pkgs_to_upgrade)
  270.                     pkgs_kept_back.append(pkg.name)
  271.             except SystemError, e:
  272.                 # can't upgrade
  273.                 logging.warning(_("package '%s' upgradable but fails to be marked for upgrade (%s)") % (pkg.name, e))
  274.                 rewind_cache(cache, pkgs_to_ugprade)
  275.                 pkgs_kept_back.append(pkg.name)
  276.                 
  277.  
  278.     pkgs = "\n".join([pkg.name for pkg in pkgs_to_upgrade])
  279.     logging.debug("pkgs that look like they should be upgraded: %s" % pkgs)
  280.            
  281.     # download what looks good
  282.     if options.debug:
  283.         fetcher = apt_pkg.GetAcquire(apt.progress.TextFetchProgress())
  284.     else:
  285.         fetcher = apt_pkg.GetAcquire()
  286.     list = apt_pkg.GetPkgSourceList()
  287.     list.ReadMainList()
  288.     recs = cache._records
  289.     pm = apt_pkg.GetPackageManager(cache._depcache)
  290.     try:
  291.         pm.GetArchives(fetcher,list,recs)
  292.     except SystemError, e:
  293.         logging.error(_("GetArchives() failed: '%s'") % e)
  294.     res = fetcher.Run()
  295.  
  296.     if dpkg_conffile_prompt():
  297.         # now check the downloaded debs for conffile conflicts and build
  298.         # a blacklist
  299.         for item in fetcher.Items:
  300.             logging.debug("%s" % item)
  301.             if item.Status == item.StatError:
  302.                 print _("An error ocured: '%s'") % item.ErrorText
  303.                 logging.error(_("An error ocured: '%s'") % item.ErrorText)
  304.             if item.Complete == False:
  305.                 print _("The URI '%s' failed to download, aborting") % item.DescURI
  306.                 logging.error(_("The URI '%s' failed to download, aborting") % item.DescURI)
  307.                 sys.exit(1)
  308.             if not os.path.exists(item.DestFile):
  309.                 print _("Download finished, but file '%s' not there?!?" % item.DestFile)
  310.                 logging.error("Download finished, but file '%s' not there?!?" % item.DestFile)
  311.                 sys.exit(1)
  312.             if item.IsTrusted == False:
  313.                 blacklisted_pkgs.append(pkgname_from_deb(item.DestFile))
  314.             if conffile_prompt(item.DestFile):
  315.                 # FIXME: skip package (means to re-run the whole marking again
  316.                 # and making sure that the package will not be pulled in by
  317.                 # some other package again!
  318.                 logging.warning(_("Package '%s' has conffile prompt and needs to be upgraded manually") % pkgname_from_deb(item.DestFile))
  319.                 blacklisted_pkgs.append(pkgname_from_deb(item.DestFile))
  320.                 pkgs_kept_back.append(pkgname_from_deb(item.DestFile))
  321.  
  322.  
  323.         # redo the selection about the packages to upgrade based on the new
  324.         # blacklist
  325.         logging.debug("blacklist: %s" % blacklisted_pkgs)
  326.         # find out about the packages that are upgradable (in a allowed_origin)
  327.         if len(blacklisted_pkgs) > 0:
  328.             cache.clear()
  329.             old_pkgs_to_upgrade = pkgs_to_upgrade[:]
  330.             pkgs_to_upgrade = []
  331.             for pkg in old_pkgs_to_upgrade:
  332.                 logging.debug("Checking (blacklist): %s" % (pkg.name))
  333.                 pkg.markUpgrade()
  334.                 if check_changes_for_sanity(cache, allowed_origins,
  335.                                             blacklisted_pkgs):
  336.                      pkgs_to_upgrade.append(pkg)
  337.                 else:
  338.                     if not (pkg.name in pkgs_kept_back):
  339.                         pkgs_kept_back.append(pkg.name)
  340.                     logging.info(_("package '%s' not upgraded") % pkg.name)
  341.                     cache.clear()
  342.                     for pkg2 in pkgs_to_upgrade:
  343.                         pkg2.markUpgrade()
  344.     else:
  345.         logging.debug("dpkg is configured not to cause conffile prompts")
  346.  
  347.     logging.debug("InstCount=%i DelCount=%i BrokenCout=%i" % (cache._depcache.InstCount, cache._depcache.DelCount, cache._depcache.BrokenCount))
  348.  
  349.     # exit if there is nothing to do and nothing to report
  350.     if (len(pkgs_to_upgrade) == 0) and (len(pkgs_kept_back) == 0):
  351.         logging.info(_("No packages found that can be upgraded unattended"))
  352.         sys.exit(0)    
  353.  
  354.     # check if we are in dry-run mode
  355.     if options.dry_run:
  356.         logging.info("Option --dry-run given, *not* performing real actions")
  357.         apt_pkg.Config.Set("Debug::pkgDPkgPM","1")
  358.  
  359.     # do the install based on the new list of pkgs
  360.     pkgs = " ".join([pkg.name for pkg in pkgs_to_upgrade])
  361.     logging.info(_("Packages that are upgraded: %s" % pkgs))
  362.  
  363.     # set debconf to NON_INTERACTIVE, redirect output
  364.     os.putenv("DEBIAN_FRONTEND","noninteractive");
  365.     setup_apt_listchanges()
  366.     
  367.     # redirect to log
  368.     REDIRECT_INPUT = os.devnull
  369.     fd = os.open(REDIRECT_INPUT, os.O_RDWR)
  370.     os.dup2(fd,0)
  371.  
  372.     now = datetime.datetime.now()
  373.     logfile_dpkg = logdir+'unattended-upgrades-dpkg_%s.log' % now.isoformat('_')
  374.     logging.info(_("Writing dpkg log to '%s'") % logfile_dpkg)
  375.     fd = os.open(logfile_dpkg, os.O_RDWR|os.O_CREAT, 0644)
  376.     old_stdout = os.dup(1)
  377.     old_stderr = os.dup(2)
  378.     os.dup2(fd,1)
  379.     os.dup2(fd,2)
  380.     
  381.     # create a new package-manager. the blacklist may have changed
  382.     # the markings in the depcache
  383.     pm = apt_pkg.GetPackageManager(cache._depcache)
  384.     if not pm.GetArchives(fetcher,list,recs):
  385.         logging.error(_("pm.GetArchives() failed"))
  386.     # run the fetcher again (otherwise local file:// 
  387.     # URIs are unhappy (see LP: #56832)
  388.     res = fetcher.Run()
  389.     # unlock the cache
  390.     try:
  391.         apt_pkg.PkgSystemUnLock()
  392.     except SystemError, e:
  393.         pass
  394.     # lock for the shutdown check - its fine if the system
  395.     # is shutdown while downloading but not so much while installing
  396.     apt_pkg.GetLock("/var/run/unattended-upgrades.lock")
  397.     # now do the actual install
  398.     error = None
  399.     try:
  400.         res = pm.DoInstall()
  401.     except SystemError,e:
  402.         error = e
  403.         res = pm.ResultFailed
  404.     finally:
  405.         os.dup2(old_stdout, 1)
  406.         os.dup2(old_stderr, 2)
  407.  
  408.     if res == pm.ResultFailed:
  409.         logging.error(_("Installing the upgrades failed!"))
  410.         logging.error(_("error message: '%s'") % e)
  411.         logging.error(_("dpkg returned a error! See '%s' for details") % logfile_dpkg)
  412.     else:
  413.         logging.info(_("All upgrades installed"))
  414.  
  415.     # send a mail (if needed)
  416.     pkg_install_success = (res != pm.ResultFailed)
  417.     send_summary_mail(pkgs, pkg_install_success, pkgs_kept_back, mem_log, logfile_dpkg)
  418.  
  419.     # auto-reboot (if required and the config for this is set
  420.     if (apt_pkg.Config.FindB("Unattended-Upgrade::Automatic-Reboot", False) and
  421.         os.path.exists("/var/run/reboot-required")):
  422.         logging.warning("Found /var/run/reboot-required, rebooting")
  423.         subprocess.call(["/sbin/reboot"])
  424.         
  425.  
  426. if __name__ == "__main__":
  427.     localesApp="unattended-upgrades"
  428.     localesDir="/usr/share/locale"
  429.     gettext.bindtextdomain(localesApp, localesDir)
  430.     gettext.textdomain(localesApp)
  431.  
  432.     if os.getuid() != 0:
  433.         print _("You need to be root to run this application")
  434.         sys.exit(1)
  435.     
  436.     if not os.path.exists("/var/log/unattended-upgrades"):
  437.         os.makedirs("/var/log/unattended-upgrades")
  438.  
  439.     # init the logging
  440.     logdir = apt_pkg.Config.FindDir("APT::UnattendedUpgrades::LogDir",
  441.                                     "/var/log/unattended-upgrades/")
  442.     logfile = logdir+apt_pkg.Config.Find("APT::UnattendedUpgrades::LogFile",
  443.                                          "unattended-upgrades.log")
  444.     logging.basicConfig(level=logging.INFO,
  445.                         format='%(asctime)s %(levelname)s %(message)s',
  446.                         filename=logfile)
  447.     # run the main code
  448.     main()
  449.